/*
 * Desc： RSA 加解密
 *  RSA加密时关于公钥和私钥的获取，存在两种方式
 *  1、生成keystore文件，获取公钥和私钥
 *
 *  2、通过Java代码：RsaUtil.generateKeyPair(null);
 *
 *      2.1 此时的公钥和私钥是通过Base64编码后的字符串，使用时需要先解码 ，得到: PrivateKey 和 PublicKey
 *      byte[] privateBytes = Base64.getDecoder().decode(DEFAULT_RSA_PRIVATE_KEY);
 *      PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);
 *      KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY);
 *      PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
 *
 *      x509代表证书
 *      byte[] publicBytes = Base64.getDecoder().decode(DEFAULT_RSA_PUBLIC_KEY);
 *      X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes);
 *      KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY);
 *      PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
 *
 *  3、数字签名。
 *     数字签名算法要求能够验证数据完整性、认证数据来源，并起到抗否认的作用。
 *     这3点与OSI参考模型中的数据完整性服务、认证（鉴别）服务和抗否认性服务相对应。
 *
 *     消息摘要算法是验证数据完整性的最佳算法，因此，消息摘要算法成为数字签名算法中的必要组成部分。
 *
 *     RSA DSA ECDSA
 *     RSA中的数字签名主要分为：MD系列 ，SHA 系列
 *     MD2withRSA  MD5withRSA
 *     SHA1withRSA SHA224withRSA SHA256withRSA SHA384withRSA SHA512withRSA
 *
 *
 * @Author jiaxin
 * @Company 亚信科技
 * @Since 2020/7/25 18:57
 *
 */
package com.ybzl.common.crypto;

import org.bouncycastle.util.encoders.Hex;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import sun.security.rsa.RSAPublicKeyImpl;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.cert.Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

public class RSAUtil {

    // 密钥库文件（jks）
    private static final String JKS_PATH = "classpath:META-INF/wangh.jks";

    // 密钥库密码
    private static final String JKS_PASSWORD = "Changeme_123";

    // 密钥库中证书的别名
    private static final String KEY_ALIAS = "wangh";

    // 密钥库中证书对应私钥获取的密钥
    private static final String CERT_PASSWORD = "Changeme_123";

    private static final String CIPHER_TYPE = "RSA";

    // 公钥信息
    private static PublicKey publicKey;

    // 私钥信息
    private static PrivateKey privateKey;

    //  RSA最大加密明文大小(key长度为1024时，所以对应的证书初始化Rsa加密算法时也需要keySize=1024)
    private static final int KEY1024_MAX_ENCRYPT_BLOCK = 117;

    //RSA最大解密密文大小(key长度为1024时，所以对应的证书初始化Rsa加密算法时也需要keySize=1024)
    private static final int KEY1024_MAX_DECRYPT_BLOCK = 128;

    // 方式1： 读取keystore中存储的公私钥
    static {
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        Resource resource = resolver.getResource(JKS_PATH);
        KeyStore keyStore = getKeyStore(resource);
        try {
            assert keyStore != null;
            // 加载密钥库中的证书(条目：wangh)
            Certificate certificate = keyStore.getCertificate(KEY_ALIAS);

            /*X509CertImpl x509Cert = (X509CertImpl) certificate;
            System.out.println(" 输出证书信息: " + x509Cert.toString());
            System.out.println(" 版本号: " + x509Cert.getVersion());
            System.out.println(" 序列号: " + x509Cert.getSerialNumber().toString(16));
            System.out.println(" 主体名： " + x509Cert.getSubjectDN());
            System.out.println(" 签发者： " + x509Cert.getIssuerDN());
            System.out.println(" 有效期： " + x509Cert.getNotBefore());
            System.out.println(" 签名算法： " + x509Cert.getSigAlgName());
            aaa = x509Cert.getSignature();
            System.out.println(" 指纹值： " + Hex.toHexString(x509Cert.getSignature()));*/ // 签名值(指纹，摘要)
            publicKey = certificate.getPublicKey();
            // 加载密钥库中指定别名对应的私钥值
            privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS, CERT_PASSWORD.toCharArray());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //方式2：读取公私钥的base64格式
    /*static {
        try {
            publicKey = getPulbickey("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuPX8sM1R+G/Y4t0rh6EMS7mNxe5NpdQ+tit7DcQ5Oazt9Z07gMXmDMN2DUvpz5G/jDw3n4pDmzqTpY8Xnk757tmHHtKdb5sujKvJ98Lmw9IhtzmhpiSWCgPcdePvgFT9G7GJgYm1aM7CCpU1tbd5dJGyG86/0spu0GzBEe3gRMQIDAQAB");
            privateKey = getPrivatekey("MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAK49fywzVH4b9ji3SuHoQxLuY3F7k2l1D62K3sNxDk5rO31nTuAxeYMw3YNS+nPkb+MPDefikObOpOljxeeTvnu2Yce0p1vmy6Mq8n3wubD0iG3OaGmJJYKA9x14++AVP0bsYmBibVozsIKlTW1t3l0kbIbzr/Sym7QbMER7eBExAgMBAAECgYAdRvALeUSFPCWPOh03AFPk5nGHLACFA8pympb7uA9zVclVNPqn6ShFxvwxiMvUlVnHiUwI3qUCY06KeN1UIBstX2eAS3XANUyhXkkidWPa1GPigWoyphueXPdKsbCb1cPoDS/AvFtrUVP42tGYBzG498AG9+OG97VP9Jih8evBwQJBANbLk4lE8lRE/cQnyNMtPz/4gwyeSAgBTzK1TvCjoM4UT4wGvy0cZR+iiIQsghwER0YWT/dihM3CklLjKrPblo0CQQDPqkvn62TCTZTf5h9ALg4D0a+tr//VzVZZYJgcZnBqC35fJ1MwAyH5kbFA95DSmgMM5sAN0cmlhA3uacZ+qv41AkArQzWkIE7uyjaSm7DHWAW1TMiYcgjwE9Nv0zTjIxHwcAbVnUZEP8Pea04z0QMjwXjZlQTmPuGgz9/I8hhXwZLBAkBpBo63L4TZBBWsGx1o/yHVaqI37JAuGFh4gXLMwj5OU5vJEgFvgdYX+0gzPEHBUWUDjFxLYPNjw2KsVlCuVVqVAkEAv5K7SRvN+f65djf/svrRwUvbf5lq3ygDxZoN4IDuAhfecTtB3qO70GY5IYxteoUPORWL5t6OWHAVv6togxznWA==");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }*/

    /***
     *  公钥加密
     * @param src 源数据
     * @return 返回值
     */
    public static String encryptoPublicKey(String src) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] result = cipher.doFinal(src.getBytes());
            return Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥解密
     *
     * @param src 源数据
     * @return 返回值
     */
    public static String decryptoPrivateKey(String src) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 需要先对数据进行base64解码
            byte[] result = cipher.doFinal(Base64.getDecoder().decode(src));
            return new String(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /***
     *  私钥加密
     * @param src
     * @return
     */
    public static String encryptoByPrivateKey(String src) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            byte[] result = cipher.doFinal(src.getBytes());
            return Base64.getEncoder().encodeToString(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 公钥加密
     *
     * @param src
     * @return
     */
    public static String decryptoByPublicKey(String src) {
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            // 需要先对数据进行base64解码
            byte[] result = cipher.doFinal(Base64.getDecoder().decode(src));
            return new String(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    // 公钥分段加密
    public static String encryptoPublicKeySplit(String src) {
        try {
            byte[] data = src.getBytes();
            // 对数据加密
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > KEY1024_MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(data, offSet, KEY1024_MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * KEY1024_MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            // 对解密数据加密后的byte数组进行base64编码
            return Base64.getEncoder().encodeToString(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥分段解密
     *
     * @param src
     * @return
     */
    public static String decryptoPrivateKeySplit(String src) {
        try {
            byte[] encryptedData = Base64.getDecoder().decode(src);
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > KEY1024_MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, KEY1024_MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * KEY1024_MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 私钥分段加密
     *
     * @param src
     * @return
     */
    public static String encryptoByPrivateKeySplit(String src) {
        try {
            byte[] data = src.getBytes();
            // 对数据加密
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段加密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > KEY1024_MAX_ENCRYPT_BLOCK) {
                    cache = cipher.doFinal(data, offSet, KEY1024_MAX_ENCRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * KEY1024_MAX_ENCRYPT_BLOCK;
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
            // 对解密数据加密后的byte数组进行base64编码
            return Base64.getEncoder().encodeToString(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /***
     *  公钥分段解密
     * @param src
     * @return
     */
    public static String decryptoByPublicKeySplit(String src) {
        try {
            byte[] encryptedData = Base64.getDecoder().decode(src);
            Cipher cipher = Cipher.getInstance(CIPHER_TYPE);
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > KEY1024_MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, KEY1024_MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * KEY1024_MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();
            return new String(decryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 基于Rsa的数字签名
     *
     * @param algorithm rsa进行数字签名时采用的算法
     *                  MD2withRSA  MD5withRSA
     *                  SHA1withRSA SHA224withRSA SHA256withRSA SHA384withRSA SHA512withRSA
     * @param src       待签名数据
     * @return 签名结果
     */
    public static String sign(String algorithm, String src) {

        try {
            // 构建签名对象
            Signature signature = Signature.getInstance(algorithm);
            signature.initSign(privateKey);
            signature.update(src.getBytes());
            byte[] result = signature.sign();
            // 对签名结果进行16进制转换
            return Hex.toHexString(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 基于Rsa的数字签名校验
     *
     * @param algorithm rsa进行数字签名时采用的算法
     * @param src       待签名数据
     * @param sign      数字签名值
     * @return 返回校验结果
     */
    public static boolean verify(String algorithm, String src, String sign) {
        try {
            Signature signature = Signature.getInstance(algorithm);
            // 公钥初始化签名对象
            signature.initVerify(publicKey);
            signature.update(src.getBytes());
            // 对数字签名值进行16进制还原
            byte[] signByte = Hex.decodeStrict(sign);
            return signature.verify(signByte);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 校验证书指纹
     *
     * @param algorithm
     * @param src
     * @param sign
     * @return
     */
    public static boolean verifyCert(String algorithm, String src, String sign) {
        Signature signature = null;
        try {
            signature = Signature.getInstance(algorithm);
            // 公钥初始化签名对象
            signature.initVerify(publicKey);
            signature.update(src.getBytes());
            // 对数字签名值进行16进制还原
            byte[] signByte = Hex.decodeStrict(sign);
            return signature.verify(signByte);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /***
     *  加载： KeyStore 文件
     * @param resource resource文件（默认.jks文件）
     * @return 密钥库信息
     */
    private static KeyStore getKeyStore(Resource resource) {
        try {
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(resource.getInputStream(), RSAUtil.JKS_PASSWORD.toCharArray());
            return ks;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成密钥对，并以字符串的形式保存
     *
     * @param keySize
     */
    public static void generateKeyPair(Integer keySize) {
        try {
            if (null == keySize) {
                keySize = Integer.parseInt("1024");
            }
            KeyPairGenerator keyPairGenerator;
            keyPairGenerator = KeyPairGenerator.getInstance(CIPHER_TYPE);
            keyPairGenerator.initialize(keySize);
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            // 获取公钥，并以base64格式打印出来
            RSAPublicKeyImpl publicKey = (RSAPublicKeyImpl) keyPair.getPublic();
            System.out.println("============公钥=============");

            System.out.println(Base64.getEncoder().encodeToString(publicKey.getEncoded()));
            // 获取私钥，并以base64格式打印出来
            System.out.println("============私钥=============");
            RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
            System.out.println(Base64.getEncoder().encodeToString(privateKey.getEncoded()));
            System.out.println("=============================");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从base64编码的公钥恢复公钥
     *
     * @param key_base64 公钥的字符串形式
     * @return
     * @throws Exception
     */
    private static PublicKey getPulbickey(String key_base64) throws Exception {
        byte[] pb = Base64.getDecoder().decode(key_base64);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pb);
        KeyFactory keyfactory = KeyFactory.getInstance(CIPHER_TYPE);
        return keyfactory.generatePublic(keySpec);
    }

    /**
     * 从base64编码的私钥恢复私钥
     *
     * @param key_base64 私钥的字符串
     * @return
     * @throws Exception
     */
    private static PrivateKey getPrivatekey(String key_base64) throws Exception {
        byte[] pb = Base64.getDecoder().decode(key_base64);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pb);
        KeyFactory keyfactory = KeyFactory.getInstance(CIPHER_TYPE);
        return keyfactory.generatePrivate(keySpec);
    }

}
